﻿using System;
using System.IO;
using System.Text;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using IronPython.Hosting;
using IronPython.Runtime.Exceptions;
using IronPython.Modules;

namespace EuphoricEngine3D
{
    public delegate void InputHandler(string str);

    public class Console : DrawableGameComponent
    {
        public event EventHandler OnClosed;
        public event EventHandler OnClosing;
        public event EventHandler OnOpened;
        public event EventHandler OnOpening;

        enum ConsoleState
        {
            Closed,
            Closing,
            Open,
            Opening
        }

        InputHandler input;
        ConsoleState _state;

        const string NewLine = "\n";
        const string Version = "EuphoricEngine Console v.1.0";
        const string Author = "Shane Simpkins";

        private Game _game;
        private SpriteFont _font;
        private SpriteBatch _spriteBatch;
        private GraphicsDevice _device;

        private Texture2D _pixel;

        private int _consoleWidth;
        private int _consoleHeight;
        private float _consoleOffset;

        private int _cursorPos;
        private int _cursorOffset;

        private int _lineWidth;
        private int _linesDisplayed = 15;

        private double _blinkerTime = 0.3;
        private float _animationTime = 3.0f;

        History _history;
        private string _inputBuffer;
        private string _outputBuffer;
        private double _firstInterval, _repeatInterval;
        private Dictionary<Keys, double> _keyTimes;

        private KeyboardState _currentKeyboardState;
        private KeyboardState _previousKeyboardState;

        public Console(Game game, SpriteFont font)
            : base(game)
        {
            this.DrawOrder = int.MaxValue;
            this.Visible = false;

            _game = game;
            _device = _game.GraphicsDevice;
            _spriteBatch = new SpriteBatch(_device);
            _font = font;

            _pixel = new Texture2D(_device, 1, 1, 1, TextureUsage.None, SurfaceFormat.Color);
            _pixel.SetData<Color>(new Color[1] { Color.White });

            _consoleWidth = _game.Window.ClientBounds.Width;
            _consoleHeight = _font.LineSpacing * _linesDisplayed;
            _consoleOffset = -(float)_consoleHeight;

            _lineWidth = (int)(_consoleWidth / _font.MeasureString("a").X);

            _inputBuffer = "";
            _history = new History();

            WriteLine(">");
            WriteLine("> " + Version);
            WriteLine("> By " + Author);
            WriteLine(">");

            _firstInterval = 500f;
            _repeatInterval = 50f;

            //used for repeating keystrokes
            _keyTimes = new Dictionary<Keys, double>();
            for (int i = 0; i < Enum.GetValues(typeof(Keys)).Length; i++)
            {
                Keys key = (Keys)Enum.GetValues(typeof(Keys)).GetValue(i);
                _keyTimes[key] = 0f;
            }

            _state = ConsoleState.Closed;
            _previousKeyboardState = _currentKeyboardState = Keyboard.GetState();
        }

        public override void Update(GameTime gameTime)
        {
            double now = gameTime.TotalRealTime.TotalSeconds;
            double elapsedTime = gameTime.ElapsedGameTime.TotalMilliseconds;

            _previousKeyboardState = _currentKeyboardState;
            _currentKeyboardState = Keyboard.GetState();

            // Handle Console Open/Close
            if (IsKeyPressed(Keys.Oem8))
            {
                if (_state == ConsoleState.Closed || _state == ConsoleState.Closing)
                {
                    if (OnOpening != null)
                        OnOpening(this, EventArgs.Empty);

                    _state = ConsoleState.Opening;
                    this.Visible = true;
                }
                else if (_state == ConsoleState.Open || _state == ConsoleState.Opening)
                {
                    if (OnClosing != null)
                        OnClosing(this, EventArgs.Empty);

                    _state = ConsoleState.Closing;
                }
            }

            // Handle Console State Animation
            if (_state == ConsoleState.Opening)
            {
                if (_consoleOffset < 0)
                    _consoleOffset += _animationTime;
                else
                {
                    _consoleOffset = 0f;
                    _state = ConsoleState.Open;

                    if (OnOpened != null)
                        OnOpened(this, EventArgs.Empty);
                }
            }
            else if (_state == ConsoleState.Closing)
            {
                if (_consoleOffset > -(float)_consoleHeight)
                    _consoleOffset -= _animationTime;
                else
                {
                    _consoleOffset = -(float)_consoleHeight;
                    _state = ConsoleState.Closed;
                    this.Visible = false;

                    if (OnClosed != null)
                        OnClosed(this, EventArgs.Empty);
                }
            }

            if (_state == ConsoleState.Open)
            {

                if (IsKeyPressed(Keys.Enter))
                {
                    if (_inputBuffer.Length > 0)
                    {
                        _history.Add(_inputBuffer);
                    }
                    WriteLine(_inputBuffer);

                    input(_inputBuffer);

                    _inputBuffer = "";
                    _cursorPos = 0;
                }

                if (KeyPressWithRepeat(Keys.Back, elapsedTime))
                {
                    if (_cursorPos > 0)
                    {
                        _inputBuffer = _inputBuffer.Remove(_cursorPos - 1, 1);
                        _cursorPos--;
                    }
                }

                if (KeyPressWithRepeat(Keys.Delete, elapsedTime))
                {
                    if (_inputBuffer.Length != 0)
                        _inputBuffer = _inputBuffer.Remove(_cursorPos, 1);
                }

                if (KeyPressWithRepeat(Keys.Up, elapsedTime))
                {
                    _inputBuffer = _history.Previous();
                    _cursorPos = _inputBuffer.Length;
                }

                if (KeyPressWithRepeat(Keys.Down, elapsedTime))
                {
                    _inputBuffer = _history.Next();
                    _cursorPos = _inputBuffer.Length;
                }

                if (KeyPressWithRepeat(Keys.Right, elapsedTime) && _cursorPos != _inputBuffer.Length)
                {
                    _cursorPos++;
                }

                if (KeyPressWithRepeat(Keys.Left, elapsedTime) && _cursorPos > 0)
                {
                    _cursorPos--;
                }

                if (IsKeyPressed(Keys.Home))
                {
                    _cursorPos = 0;
                }

                if (IsKeyPressed(Keys.End))
                {
                    _cursorPos = _inputBuffer.Length;
                }

                string nextChar = GetStringFromKeyState(elapsedTime);

                if (!string.IsNullOrEmpty(nextChar))
                {
                    if (_inputBuffer.Length == _cursorPos)
                    {
                        _inputBuffer += nextChar;
                    }
                    else
                    {
                        _inputBuffer = _inputBuffer.Insert(_cursorPos, nextChar);
                    }
                    _cursorPos += nextChar.Length;
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            double now = gameTime.TotalRealTime.TotalSeconds;

            _consoleWidth = _game.Window.ClientBounds.Width;
            _consoleHeight = _font.LineSpacing * _linesDisplayed;
            _lineWidth = (int)((_consoleWidth - 20) / _font.MeasureString("a").X);

            _spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

            // Draw Console Background
            _spriteBatch.Draw(_pixel, new Rectangle(0, (int)_consoleOffset, _consoleWidth, _consoleHeight), new Color(0, 0, 0, 125));

            // Draw Console Edge
            _spriteBatch.Draw(_pixel, new Rectangle(0, (int)_consoleOffset + _consoleHeight, _consoleWidth, 2), Color.OrangeRed);

            // Draw Cursor
            _spriteBatch.DrawString(_font, DrawCursor(now), new Vector2(0, _consoleOffset + _consoleHeight - _font.LineSpacing), Color.White);

            // Draw Text
            //DrawLines(Render(_outputBuffer + _inputBuffer));

            _spriteBatch.End();

            // Draw Text With MultiColor Formatting, But Force InputBuffer To White!
            //DrawFormatedText(Render("![#FF0000]" + _outputBuffer + "![#FFFFFF]" + _inputBuffer));

            TextHandler.spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            
            int count = Render(TextHandler.StripTextCodes(_outputBuffer + _inputBuffer)).Count;

            int StrippedCharCount;
            TextHandler.DrawTextParsedColored(
                "![#FF0000]" + _outputBuffer + "![#FFFFFF]" + TextHandler.StripTextCodes(_inputBuffer, out StrippedCharCount),
                0, (int)(_consoleOffset + (_consoleHeight - 10) - _font.LineSpacing * count),
                "ConsoleFont",
                _consoleWidth
            );

            //_cursorPos -= StrippedCharCount; // HMM ??????

            TextHandler.spriteBatch.End();

            _game.GraphicsDevice.RenderState.DepthBufferEnable = true;
        }

        private void DrawLines(List<string> lines)
        {
            int j = 0;
            foreach (string str in lines)
            {
                j++;
                _spriteBatch.DrawString(_font, str, new Vector2(0, _consoleOffset + _consoleHeight - _font.LineSpacing * (j)), Color.White);
            }
        }

        private void DrawFormatedText(List<string> lines)
        {
            TextHandler.spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
            int j = 0;
            foreach (string str in lines)
            {
                j++;
                float xPos = 0;
                float yPos = _consoleOffset + _consoleHeight - _font.LineSpacing * (j);
                TextHandler.DrawTextParsedColored(str, (int)xPos, (int)yPos, "ConsoleFont", _consoleWidth);
            }
            TextHandler.spriteBatch.End();
        }

        public List<string> Render(string output)
        {
            List<string> lines = WrapLine(output, _lineWidth);
            for (int i = 0; i < lines.Count; i++)
            {
                lines[i] = lines[i].Replace("\t", "    ");
            }
            lines.Reverse();
            return lines;
        }

        public string DrawCursor(double now)
        {
            int spaces = (_inputBuffer.Length > 0 && _cursorPos > 0) ?
                Render(_inputBuffer.Substring(0, _cursorPos))[0].Length + _cursorOffset :
                _cursorOffset;

            return new String(' ', spaces) + (((int)(now / _blinkerTime) % 2 == 0) ? "_" : "");
        }

        public void Prompt(string str, InputHandler callback)
        {
            Write(str);
            string[] lines = WrapLine(_outputBuffer, _lineWidth).ToArray();
            this.input = callback;
            _cursorOffset = lines[lines.Length - 1].Length;
        }

        public string Chomp(string str)
        {
            if (str.Length > 0 && str.Substring(str.Length - 1, 1) == "\n")
            {
                return str.Substring(0, str.Length - 1);
            }
            return str;
        }

        private List<string> WrapLine(string line, int columns)
        {
            List<string> wraplines = new List<string>();
            if (line.Length > 0)
            {
                wraplines.Add("");
                int lineNum = 0;

                for (int i = 0; i < line.Length; i++)
                {
                    string ch = line.Substring(i, 1);

                    if (ch == "\n" || wraplines[lineNum].Length > columns)
                    {
                        wraplines.Add("");
                        lineNum++;
                    }
                    else
                    {
                        wraplines[lineNum] += ch;
                    }
                }
            }

            return wraplines;
        }

        private List<string> WrapLines(string[] lines, int columns)
        {
            List<string> wraplines = new List<string>();
            foreach (string line in lines)
            {
                wraplines.AddRange(WrapLine(line, columns));
            }
            return wraplines;
        }

        public void WriteError(string str)
        {
            WriteLine("![#FF0000]" + str);
        }

        public void Write(string str)
        {
            //_outputBuffer += str;
            Write(str, Color.White);
        }
        public void Write(string str, Color color)
        {
            string colorStr = string.Empty;

            // Get Hex Color
            // colorStr = string.format("![#{0}{1}{2}]", "", "", "");

            _outputBuffer += colorStr + str;
        }

        public void WriteLine(string str)
        {
            //Write(str + NewLine);
            WriteLine(str, Color.White);
        }
        public void WriteLine(string str, Color color)
        {
            string colorStr = string.Empty;

            // Get Hex Color
            // colorStr = string.format("![#{0}{1}{2}]", "", "", "");

            Write(str + NewLine);
        }

        public void Clear()
        {
            _outputBuffer = "";
        }

        public void ClearHistory()
        {
            _history.Clear();
        }

        private string GetStringFromKeyState(double elapsedTime)
        {
            bool shiftPressed = _currentKeyboardState.IsKeyDown(Keys.LeftShift) || _currentKeyboardState.IsKeyDown(Keys.RightShift);
            bool altPressed = _currentKeyboardState.IsKeyDown(Keys.LeftAlt) || _currentKeyboardState.IsKeyDown(Keys.RightAlt);

            foreach (KeyBinding binding in KeyboardHelper.EnglishBindings)
                if (KeyPressWithRepeat(binding.Key, elapsedTime))
                {
                    if (!shiftPressed && !altPressed)
                        return binding.UnmodifiedString;
                    else if (shiftPressed && !altPressed)
                        return binding.ShiftString;
                    else if (!shiftPressed && altPressed)
                        return binding.AltString;
                    else if (shiftPressed && altPressed)
                        return binding.ShiftAltString;
                }

            return "";
        }

        private bool IsKeyPressed(Keys key)
        {
            return _currentKeyboardState.IsKeyDown(key) && _previousKeyboardState.IsKeyUp(key);
        }

        private bool KeyPressWithRepeat(Keys key, double elapsedTime)
        {
            if (_currentKeyboardState.IsKeyDown(key))
            {
                if (IsKeyPressed(key)) return true;
                _keyTimes[key] -= elapsedTime;
                double keyTime = _keyTimes[key];
                if (_keyTimes[key] <= 0)
                {
                    _keyTimes[key] = _repeatInterval;
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                _keyTimes[key] = _firstInterval;
                return false;
            }
        }

        public class History
        {
            List<string> history;
            int index = 0;

            public string Current
            {
                get { if (index < history.Count) { return history[index]; } else { return ""; } }
            }

            public History()
            {
                history = new List<string>();
            }

            public void Add(string str)
            {
                history.Add(str);
                index = history.Count;
            }

            public string Previous()
            {
                if (index > 0)
                {
                    index--;
                }
                return Current;
            }

            public string Next()
            {
                if (index < history.Count - 1)
                {
                    index++;
                }
                return Current;
            }

            public void Clear()
            {
                history.Clear();
            }
        }
    }
}
